<template>
  <q-table ref="QxTable" v-model:expanded="expanded" v-model:pagination="pagination" v-model:selected="selected"
           :class="className" :columns="formatColumns" :grid="option.grid" :loading="loading" :row-key="rowKey ?? 'id'"
           :rows="rows" :selection="selection" :separator="separator" :style="style" :virtual-scroll="isVirtual"
           :virtual-scroll-sticky-size-start="startSize" :visible-columns="dynamicColumns" dense hide-pagination
           wrap-cells @request="onRequest" @selection="onSelection" @update:selected="e => emits('updateSelected', e)">
    <template v-if="!nonTopSpace" v-slot:top="topProps">
      <table-top v-model:dynamicColumns="dynamicColumns" v-model:isFullscreen="isFullscreen" :columns="formatColumns"
                 :dynamicBtn="option.dynamicBtn" :fullscreenBtn="option.fullscreenBtn" :multiColumn="multiColumn"
                 :props="topProps" :refreshBtn="option.refreshBtn" :rightBtn="option.rightBtn"
                 :searchBtn="option.searchBtn" @refresh="onRequest">
        <template v-if="slots.top" v-slot:top>
          <slot :props="topProps" name="top"></slot>
          <q-separator v-if="topSeparator"></q-separator>
        </template>
        <template v-slot:menuLeft>
          <slot :selected="selected" name="menuLeft"></slot>
          <q-export v-if="option.export" :columns="columns" :data="rows" :exportMethod="remoteInfo?.dicMethod"
                    :exportQuery="formatExportQuery" :exportText="option.exportText"
                    :exportUrl="exportUrl || remoteInfo?.dicUrl" :fileName="fileName" :isFormat="option.isFormat"
                    :isStream="option.isStream" :merges="option.merges" :multiHeader="option.multiHeader"
                    :printHeader="option.printHeader"></q-export>
          <slot :selected="selected" name="menuLeftR"></slot>
        </template>
        <template v-slot:menuRight="{ btnAttr }">
          <slot :btnAttr="btnAttr" :props="topProps" :selected="selected" name="menuRight"></slot>
        </template>
        <template v-slot:menuRightR="{ btnAttr }">
          <q-export v-if="option.exportRight" :columns="columns" :data="rows" :exportMethod="remoteInfo?.dicMethod"
                    :exportQuery="formatExportQuery" :exportText="option.exportText"
                    :exportUrl="exportUrl || remoteInfo?.dicUrl" :fileName="fileName" :isFormat="option.isFormat"
                    :isStream="option.isStream" :merges="option.merges" :multiHeader="option.multiHeader"
                    :printHeader="option.printHeader" v-bind="btnAttr"></q-export>
          <slot :btnAttr="btnAttr" :selected="selected" name="menuRightR"></slot>
        </template>
      </table-top>
    </template>
    <template v-slot:header="headProps">
      <template v-for="(columns, index) in multiColumn" :key="index">
        <table-th v-model:fixedColsInfo="fixedColsInfo" :columns="columns" :header-cell-click="headerCellClick"
                  :header-click="headerClick" :index="index" :props="headProps" :selection="selection">
          <template v-slot:selection>
            <q-checkbox v-model="headProps.selected" :disable="option.selectable === false" dense
                        @update:model-value="e => emits('header-checkbox', e, selected)" />
          </template>
          <template v-for="column in filterColumns(columns, 'H')" :key="column.name" v-slot:[getKey(column)]>
            <slot :column="column" :name="getKey(column)"></slot>
          </template>
        </table-th>
      </template>
    </template>
    <template v-slot:body="bodyProps">
      <table-td v-model:fixedColsInfo="fixedColsInfo" :canExpand="option.canExpand" :cell-click="cellClick"
                :cellDbClick="cellDbClick" :child-key="option.childKey" :db-row-click="dbRowClick"
                :merge-cells="mergeFunc(bodyProps)" :props="bodyProps" :row-click="rowClick" :row-cls="rowCls"
                :selection="selection" :tree-node="option.treeNode" v-bind="$attrs" @toggle-expand="toggleExpand">
        <template v-slot:selection>
          <q-checkbox v-model="bodyProps.selected" :disable="!isCheckbox(bodyProps.row)" dense />
        </template>
        <template v-for="column in filterColumns(bodyProps.cols)" :key="column.name" v-slot:[column.field]>
          <slot v-if="column.slot" :index="bodyProps.rowIndex" :name="column.field" :row="bodyProps.row"></slot>
        </template>
        <template v-slot:menu>
          <slot :index="bodyProps.rowIndex" :row="bodyProps.row" name="menu"></slot>
        </template>
      </table-td>
      <template v-if="option.canExpand">
        <tr v-show="bodyProps.expand" class="bg-transparent q-tr--no-hover">
          <slot :expand="bodyProps.expand" :index="bodyProps.rowIndex" :row="bodyProps.row" name="expandRow"></slot>
        </tr>
      </template>
    </template>
    <template v-slot:item="itemProps">
      <table-grid :cardslot="option.cardslot" :cell-click="cellClick" :cellDbClick="cellDbClick"
                  :child-key="option.childKey" :db-row-click="dbRowClick" :gridCols="gridCols"
                  :headerWidth="option.headerWidth" :nonHeader="option.nonHeader" :props="itemProps"
                  :row-click="rowClick" :row-cls="rowCls" :selection="selection" :tree-node="option.treeNode"
                  v-bind="$attrs" @toggle-expand="toggleExpand">
        <template v-slot:selection>
          <q-checkbox v-model="itemProps.selected" :disable="!isCheckbox(itemProps.row)" dense />
        </template>
        <template v-if="slots.topRight" v-slot:topRight>
          <slot :index="itemProps.rowIndex" :row="itemProps.row" name="topRight"></slot>
        </template>
        <template v-for="column in filterColumns(itemProps.cols)" :key="column.name" v-slot:[column.field]>
          <slot v-if="column.slot" :index="itemProps.rowIndex" :name="column.field" :row="itemProps.row"></slot>
        </template>
        <template v-if="slots.menu" v-slot:menu>
          <slot :index="itemProps.rowIndex" :row="itemProps.row" name="menu"></slot>
        </template>
      </table-grid>
    </template>
    <template v-slot:no-data="{ message }">
      <MEmpty :emptyText="message" classes="q-pb-md"></MEmpty>
    </template>
    <template v-slot:loading>
      <q-inner-loading showing>
        <q-spinner-ios color="primary" size="3em" />
      </q-inner-loading>
    </template>
    <template v-slot:bottom>
      <div v-if="!hideBtm" :class="hideBtm ? '' : 'q-pb-sm'"
           class="qx-table-bottom row justify-between relative-position">
        <div class="row q-gutter-x-sm items-center">
          <span v-if="selected.length" class="selected-tips text-body1">
            {{ $t('SelectedTips', [selected.length]) }}
            <q-btn :disable="option.selectable === false" :label="$t('Empty')" color="primary" flat padding="0"
                   @click="clearSelection" />
          </span>
          <slot name="bottomLeft"></slot>
        </div>
        <div v-if="isPage" class="q-gutter-x-sm row items-center">
          <div class="text-body1">{{ $t('Totals') }}: {{ pagination.rowsNumber || 0 }}</div>
          <q-select v-model="pagination.rowsPerPage" :options="pageOptions || options"
                    popup-content-class="pagination-select" popup-content-style="height: 10rem;" v-bind="selBind"
                    @update:model-value="onRequest()">
            <template v-slot:selected>
              {{ `${pagination.rowsPerPage}${$t('ItemPage')}` }}
            </template>
            <template v-slot:option="{ itemProps, opt }">
              <q-item v-bind="itemProps">
                <q-item-section>{{ `${opt}${$t('ItemPage')}` }}</q-item-section>
              </q-item>
            </template>
          </q-select>
          <q-pagination v-model="pagination.page" :max="maxPages" :max-pages="6" active-color="primary"
                        active-design="outline" color="grey-8" ellipses gutter=".5rem" padding=".375rem .125rem"
                        @update:model-value="onRequest()"></q-pagination>
        </div>
      </div>
    </template>
  </q-table>
  <q-resize-observer v-if="option.grid" @resize="onResize" />
</template>

<script setup>
import { i18n } from 'boot/i18n'
import TableGrid from 'components/Non-Global/TableGrid.vue'
import TableTd from 'components/Non-Global/TableTd.vue'
import TableTh from 'components/Non-Global/TableTh.vue'
import TableTop from 'components/Non-Global/TableTop.vue'
import { convertToRows, selectable, treeConvertList } from 'components/tableUtils'
import { dom, throttle } from 'quasar'
import { getAPI, postAPI } from 'src/api'
import { deepClone } from 'src/utils/utils'
import { computed, reactive, ref, useSlots, watch, watchPostEffect } from 'vue'

const props = defineProps({
  data: Array,
  class: String,
  nonTop: Boolean,
  fileName: String,
  exportUrl: String,
  rowClick: Function,
  nonBottom: Boolean,
  pageOptions: Array,
  rowsPerPage: Number,
  cellClick: Function,
  dbRowClick: Function,
  formatData: Function,
  mergeCells: Function,
  headerClick: Function,
  cellDbClick: Function,
  headerCellClick: Function,
  rowKey: [String, Function],
  rowCls: [String, Function],
  /**
   * 远程获得表格数据
   * @param {string} dicUrl 接口地址, 必需
   * @param {string} dicMethod 请求方式, 可选, 默认POST
   * @param {Object} params 请求参数, 可选
   * @param {Boolean} clearSelected 是否清空已勾选数据, 可选
   */
  remoteData: [Function, Object],
  separator: { type: String, default: 'none' },
  option: { type: Object, default: () => ({}) }
})
const QxTable = ref(null)
const emits = defineEmits(['header-checkbox', 'updateSelected'])

const slots = useSlots()
const expanded = ref([])
const pages = [1, 2, 4, 10, 20]
const rows = ref(props.data || [])
const screenSize = ref({ width: 0, height: 0 })

const cloneColumn = computed(() => {
  const { index = true, column = [], menu, menuWidth = 80, menuText = i18n.t('Operation') } = props.option || {}
  const columns = deepClone(column)
  if (index) {
    const style = 'width: 3.125rem; min-width: 3.125rem;'
    columns.unshift({
      name: 'index', label: '#', showColumn: true, headerStyle: style, style, align: 'center',
      field: 'index', export: false
    })
  }
  if (menu) {
    const style = `width: ${menuWidth}px; min-width: ${menuWidth}px`
    columns.push({
      name: 'menu', label: menuText, headerStyle: style, showColumn: true, style, align: 'center',
      field: 'menu', export: false
    })
  }
  return columns
})

const fixedColsInfo = ref({})
const dynamicColumns = ref([])
const isFullscreen = ref(false)
const isVirtual = computed(() => rows.value.length > 100)
const multiColumn = computed(() => convertToRows(cloneColumn.value))
const isCheckbox = computed(() => row => selectable(props.option.selectable, row))
const columns = computed(() => treeConvertList(props.option.align, cloneColumn.value))
const formatColumns = computed(() => columns.value.filter(el => !el.hide))
const startSize = computed(() => {
  if (!isVirtual.value || !QxTable.value) return 0
  const node = QxTable.value.$el.querySelector('thead')
  return node ? dom.height(node) : 0
})
const nonTopSpace = computed(() => {
  const { nonTop = false } = props
  if (nonTop) return true
  const { rightBtn, export: exportBtn } = props.option || {}
  if (rightBtn || exportBtn) return false
  const { top, menuLeft, menuLeftR, menuRight, menuRightR } = slots
  return !(top || menuLeft || menuLeftR || menuRight || menuRightR)
})
const topSeparator = computed(() => {
  const { rightBtn, export: exportBtn } = props.option || {}
  if (rightBtn || exportBtn) return true
  const { menuLeft, menuLeftR, menuRight, menuRightR } = slots
  return !!(menuLeft || menuLeftR || menuRight || menuRightR)
})
const mergeFunc = computed(() => bodyProps => {
  const { mergeCells } = props
  if (!mergeCells || typeof mergeCells !== 'function') return {}
  const { cols = [], row = {}, pageIndex = 0 } = bodyProps || {}
  return mergeCells({ cols, row, pageIndex, rows: rows.value })
})
const hideBtm = computed(() => {
  if (props.nonBottom) return true
  if (!rows.value.length) return false
  if (selected.value.length) return false
  return !(slots.bottomLeft || isPage.value)
})
const remoteInfo = computed(() => {
  const { remoteData } = props
  if (typeof remoteData === 'function') return remoteData()
  if (typeof remoteData === 'object') return remoteData
  return undefined
})
const isPage = computed(() => props.option?.page !== false && remoteInfo.value)
const formatGridWidth = { 600: 1, 1024: 2, 1440: 3, 1650: 4, 1920: 5 }
const gridCols = computed(() => {
  const { grid = false, gridCols } = props.option || {}
  if (!grid || gridCols) return gridCols || 5
  const { width } = screenSize.value
  for (const key in formatGridWidth) {
    if (width < key) return formatGridWidth[key]
  }
  return 6
})
const pagination = ref({
  page: 1, rowsPerPage: isPage.value ? (props.rowsPerPage || gridCols.value * 4) : 0,
  rowsNumber: isPage.value ? 0 : void 0, sortBy: '', descending: false
})
const maxPages = computed(() => {
  const totalsPage = pagination.value.rowsNumber / (pagination.value.rowsPerPage || 1)
  return Math.ceil(totalsPage) || 1
})
const options = computed(() => pages.map(el => el * gridCols.value))
watch(gridCols, () => (pagination.value.rowsPerPage = gridCols.value * 4) && onRequest())

const loading = defineModel('loading', { type: Boolean, default: false })
const selected = defineModel('selected', { type: Array, default: () => [] })
const selection = computed(() => props.option.selection)
const onSelection = e => {
  const { selectable: func } = props.option || {}
  if (func === false) return selected.value.splice(0)
  if (func === true || typeof func !== 'function' || !e.added) return
  selected.value = e.rows.filter(el => selectable(func, el))
}

const className = computed(() => {
  let cls = 'qx-table overflow-hidden'
  const { class: classes = '' } = props || {}
  if (!classes.includes('non-margin-top')) {
    if (nonTopSpace.value) cls += ' q-table-mt-md'
    else if (topSeparator.value) cls += ' q-table-mt-sm'
  }
  if (isFullscreen.value) cls += ' qx-full-table'
  if (!isPage.value) cls += ' non-page-table'
  return `${cls} ${classes}`
})
const style = computed(() => {
  if (isFullscreen.value) return
  const { height, maxHeight = '100%' } = props.option || {}
  if (height) return { height }
  if (maxHeight) return { maxHeight }
  return {}
})

const getKey = computed(() => column => `${column.field}H`)
const filterColumns = computed(() => (columns = [], str = '') => {
  return str === 'H' ? columns.filter(el => el.headerSlot) : columns.filter(el => el.slot)
})

const getDynamicColumns = columns => (dynamicColumns.value = columns.filter(el => !el.required).map(el => el.name))
watchPostEffect(() => getDynamicColumns(formatColumns.value))

const formatExportQuery = () => {
  const params = remoteInfo.value.params
  if (typeof params === 'string') return ''
  const { exportQuery = null } = props.option || {}
  if (exportQuery === null) return params
  const params2 = typeof exportQuery === 'function' ? exportQuery() : exportQuery
  if (typeof params2 === 'string') return ''
  return { ...params, ...(params2 || {}) }
}
const formatRows = async (data, done) => {
  const { formatData } = props
  const object = typeof formatData === 'function' ? await formatData(data, pagination) || {} : data
  rows.value = object.rows || object.data || (Array.isArray(object) ? object : [])
  pagination.value.page = +(object.pageNum || 1)
  pagination.value.rowsNumber = +(object.total || 0)
  if (done) done()
  loading.value = false
}

const onRequest = throttle((tableProps = { pagination: pagination.value }) => {
  const { sortBy, descending } = tableProps.pagination
  expanded.value = []
  pagination.value.sortBy = sortBy
  pagination.value.descending = descending
  if (!remoteInfo.value) return (rows.value = props.data || [])
  if (remoteInfo.value.clearRows) {
    rows.value = []
    delete remoteInfo.value.clearRows
  }
  if (remoteInfo.value.clearSelected) {
    clearSelection()
    delete remoteInfo.value.clearSelected
  }
  if (typeof remoteInfo.value.params === 'string') return
  loading.value = true
  const nowTime = Date.now() + 1000
  const tempRemote = deepClone(remoteInfo.value)
  const API = tempRemote.dicMethod === 'get' ? getAPI : postAPI
  tempRemote.params = tempRemote.params || {}
  if (isPage.value) {
    tempRemote.params.pageNum = pagination.value.page
    tempRemote.params.pageSize = pagination.value.rowsPerPage
  } else tempRemote.params.pageSize = '-1'
  API(tempRemote.dicUrl, tempRemote.params, data => {
    const TD = nowTime - Date.now() // 判断接口调用时间是否超过1s，不超过则手动延时
    if (TD > 0) {
      let timer = setTimeout(() => {
        formatRows(data, tempRemote.done)
        clearTimeout(timer)
        timer = null
      }, TD)
    } else formatRows(data, tempRemote.done)
  }, () => {
    rows.value = []
    loading.value = false
  })
}, 1000)

watch(() => props.data, () => onRequest())
watch(() => props.remoteData, () => onRequest(), { deep: true })

const onResize = size => screenSize.value = size
const toggleExpand = (prop = {}, childKey) => {
  if (props.option.canExpand) {
    prop.expand = !prop.expand
    return
  }
  if (prop.expand) {
    prop.expand = false
    expanded.value = expanded.value.filter(el => el !== prop.key)
    const { parentId = 'parentIds' } = props.option || {} // parentId 指数据中存的父节点的id集合(逗号拼接字符串) 默认parentIds
    rows.value = rows.value.filter(el => !el[parentId].split(',').includes(prop.key))
    return
  }
  prop.expand = true
  if ([0, '0'].includes(prop.row.level)) {
    expanded.value = [prop.key]
    rows.value = rows.value.filter(el => [0, '0'].includes(el.level))
  }
  const index = rows.value.findIndex(el => el.id === prop.key) // 树形表格必须以id作为主键 即rowKey
  rows.value.splice(index + 1, 0, ...prop.row[childKey])
}

const selBind = reactive({ outlined: true, 'options-dense': true, dense: true })
const clearSelection = () => QxTable.value && QxTable.value.clearSelection()
defineExpose({ clearSelection })
</script>